25

I know how to seed data to a database with old .NET 5.0 in startup.cs file using my Seeder class with a Seed() method creating some initial data.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, Seeder seeder)
{
   seeder.Seed();

   ..............
   // other configurations
}

How do I do this in .NET 6.0? There is no place to add my Seeder class as an argument.

JerZaw
  • 515
  • 1
  • 4
  • 9
  • i believe its part of a code migration. – Daniel A. White Jan 04 '22 at 16:21
  • 1
    well of course it is, but still I don't know how to do it and I can't find an answer anywhere – JerZaw Jan 04 '22 at 16:23
  • 2
    https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding – Daniel A. White Jan 04 '22 at 16:24
  • keep in mind that old EF had an AddOrUpdate() extension, but in ef core it's just Update(). https://stackoverflow.com/questions/62449078/what-is-the-alternate-for-addorupdate-method-in-ef-core – Jeff Jan 04 '22 at 21:50
  • What do you want to build? Razor pages or MVC. Razor https://learn.microsoft.com/en-us/aspnet/core/data/ef-rp/intro?view=aspnetcore-6.0&tabs=visual-studio#create-the-database MVC https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro?view=aspnetcore-6.0#initialize-db-with-test-data – tomdinh Sep 25 '22 at 13:57

5 Answers5

18

I have never use your solution before. This is what I'm doing,

public class DataContext: DbContext
{
    public DataContext(DbContextOptions options) : base(options)
    {
        
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        new DbInitializer(modelBuilder).Seed();
    }
    
    // Db sets
}

And

public class DbInitializer
{
    private readonly ModelBuilder modelBuilder;

    public DbInitializer(ModelBuilder modelBuilder)
    {
        this.modelBuilder = modelBuilder;
    }

    public void Seed()
    {
        modelBuilder.Entity<User>().HasData(
               new User(){ Id = 1.... },
               new User(){ Id = 2.... },

        );
    }
}

Then run the command

dotnet ef migrations add .......

To create migration file

And

dotnet ef database update

To update db

Võ Quang Hòa
  • 2,688
  • 3
  • 27
  • 31
  • With this approach, I'm finding that the normalized columns are not being populated, even if I explicitly set a value. Is there any way around that, so these columns get populated? (e.g. `NormalizedEmail`) – devklick Aug 22 '22 at 14:18
  • 1
    @devklick I don't really understand your issue but I think it's unrelated. First, you have to make sure the data structure is completely synchronized from the code to the database tables with the migration commands. Second, to see how the migration works, you can run the script command https://learn.microsoft.com/en-us/ef/core/cli/dotnet#dotnet-ef-migrations-script – Võ Quang Hòa Aug 23 '22 at 15:31
  • 1
    You're totally right, this was an unrelated issue - my apologies. I hadnt created a new migration after updating my code to set the normalized values. Thanks for your suggestions anyway. – devklick Aug 23 '22 at 16:29
  • I feel like maybe there should be a try/catch in here somewhere? – Scottish Smile Aug 06 '23 at 12:34
18

I solved a similar problem as follows:

Program.cs (.NET 6)

...
builder.Services.AddScoped<IDbInitializer, DbInitializer>(); //can be placed among other "AddScoped" - above: var app = builder.Build();   

...    
SeedDatabase(); //can be placed above app.UseStaticFiles();
...    
    void SeedDatabase() //can be placed at the very bottom under app.Run()
    {
        using (var scope = app.Services.CreateScope())
        {
            var dbInitializer = scope.ServiceProvider.GetRequiredService<IDbInitializer>();
            dbInitializer.Initialize();
        }
    }
Virtual
  • 189
  • 3
12

.NET 6.0

Use these links for detailed version:

Program.cs

var builder = WebApplication.CreateBuilder(args);

//Add services to the container.
builder.Services.AddDbContext<YourDbContext>(
    optionsBuilder => optionsBuilder.UseSqlServer("Your connection string goes here") //install - Microsoft.EntityFrameworkCore.SqlServer to use ".UseSqlServer" extension method
builder.Services.AddScoped<DbInitializer>();

var app = builder.Build();

//Configure the HTTP-request pipeline
if (app.Environment.IsDevelopment())
{
    app.UseItToSeedSqlServer();    //custom extension method to seed the DB
    //configure other services
}

app.Run();

DbInitializerExtension.cs

internal static class DbInitializerExtension
{
    public static IApplicationBuilder UseItToSeedSqlServer(this IApplicationBuilder app)
    {
        ArgumentNullException.ThrowIfNull(app, nameof(app));

        using var scope = app.ApplicationServices.CreateScope();
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<YourDbContext>();
            DbInitializer.Initialize(context);
        }
        catch (Exception ex)
        {

        }

        return app;
    }
}

DbInitializer.cs

internal class DbInitializer
{
    internal static void Initialize(YourDbContext dbContext)
    {
        ArgumentNullException.ThrowIfNull(dbContext, nameof(dbContext));
        dbContext.Database.EnsureCreated();
        if (dbContext.Users.Any()) return;

        var users = new User[]
        {
            new User{ Id = 1, Name = "Bruce Wayne" }
            //add other users
        };

        foreach(var user in users)
            dbContext.Users.Add(user);

        dbContext.SaveChanges();
    }
}
phougatv
  • 881
  • 2
  • 12
  • 29
  • Repeats two other answers. Also, this is not the only seeding method. It should be understood in the light of the entire tutorial and it should be explained when this method is chosen and not other methods. – Gert Arnold May 26 '22 at 10:03
  • 2
    Considering this answer seems the most complete, and easiest to follow, I give them my vote. Plus, the question doesn't ask for an explanation comparing all possible solutions, so why is this the only answer that seems to have that as a requirement when the other answers give even less info? – Will Aug 19 '22 at 16:41
  • 1
    I wish I could downvote comments. This answer is not a repeat of two other answers - it combines aspects of two other answers and adds useful refactors like implementation as an extension method and only seeding in Dev. As such it's not only the best answer, but arguably the only complete answer. Very few, if any, answers on this platform adhere to the standards requested by Gert's comment. – FredM Jan 05 '23 at 04:15
4

In my case, I'm using Microsoft.AspNetCore.Identity and needed initialize application with default values in database using seed methods.

builder.Services.AddScoped<UserManager<ApplicationUser>>();
builder.Services.AddScoped<RoleManager<IdentityRole>>();

And after line containing

var app = builder.Build();

I have called the seeds methods:

using (var scope = app.Services.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<Usuario>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
await DefaultRoles.SeedAsync(roleManager);
await DefaultAdmin.SeedAsync(userManager);
}
  • Doesnt this mean that your application will attempt to add the seed data every time it starts? I guess your `DefaultRoles` and `DefaultAdmin` classes can take care of idempotency checks, but it seems wrong to do this every time the application starts. It should probably be `OnModelCreating` in the context class. – devklick Aug 22 '22 at 14:22
2

Seeder.cs

public static class Seeder
{
public static void Initialize(DatabaseContext context)
{
    context.Database.EnsureCreated();
    //your seeding data here
    context.SaveChanges();

}
}

Program.cs

var app = builder.Build();
SeedDatabase();

void SeedDatabase()
    using(var scope = app.Services.CreateScope())
    try{
        var scopedContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
        Seeder.Initialize(scopedContext);
        }
catch{
    throw;
}

as simple as it gets before using DI.

RChamy
  • 31
  • 3