3

I was thinking about just doing a migration, and seeding the data in that migration. I am not sure if i want seeding to be part of my migrations, maybe there comes a time when i want a clean slate.

Last time i did asp.net about a year ago on windows i had the following implementation:

using System.Collections.Generic;
using System.Data.Entity.Validation;
using Mentor.Models;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
/**
 * Author: matti
 */
namespace Mentor.Migrations
{
    using System;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;

    internal sealed class Configuration : DbMigrationsConfiguration<Mentor.Models.ApplicationDbContext>
    {
        public Configuration()
        {
            /*if (System.Diagnostics.Debugger.IsAttached == false)
               System.Diagnostics.Debugger.Launch();*/
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
            ContextKey = "Mentor.Models.ApplicationDbContext";
        }

        protected override void Seed(Mentor.Models.ApplicationDbContext context)
        {
            try
            {
                var passwordHasher = new PasswordHasher();
                User user1 = new User()
                {
                    UserName = "mattinielsen5@hotmail.com",
                    PasswordHash = passwordHasher.HashPassword("Denherpderp21!"),
                    FirstName = "Matti andreas",
                    LastName = "Nielsen",
                    Age = 24,
                    ProfileText = "Lorem ipsum dolor sit amet, minimum delicatissimi ad eos, " +
                                  "ne veniam eirmod voluptatibus vel, ne eam facilisi inciderint. " +
                                  "Ex eleifend recteque delicatissimi eos, ut erat posse etiam pri." +
                                  " Ei qui commune vivendum legendos, augue accusata in vim, mei at" +
                                  " bonorum pericula definitionem. Has ornatus aliquando vulputate " +
                                  "at, nonumes docendi in mel. Ne duo recusabo percipitur, et nam " +
                                  "vitae nostrud cotidieque, cibo liber mel te.",
                    IsMentor = true,
                    IsMentee = false,
                    UndefinedInterests = new List<Interest>
                    {

                    },
                    MentorInterests = new List<Interest>
                    {

                    },
                    ... blabla alot of entities ...
                context.SaveChanges();
            }
            catch (DbEntityValidationException e)
            {
                //some error handling
            }
        }
    }
}

So i want something like a seed method, so i was thinking about making my own seed method being called in startup.cs depending on some environment variable like development. My question is, how do you guys do it - or how would you do it??

EDIT:

I am considering doing it like this, when creating the model:

protected override void OnModelCreating(ModelBuilder modelBuilder) 
  {
    //One-to-one
     modelBuilder.Entity<Account>().HasOne(a => a.Player).WithOne(p =>   p.Account).HasForeignKey<Player>(p => p.AccountForeignKey);
     modelBuilder.Entity<Group>().HasOne(g => g.Role).WithOne(r => r.Group).HasForeignKey<Role>(r => r.GroupForeignKey);
    modelBuilder.Entity<GameEvent>().HasOne(e => e.Event);
    modelBuilder.Entity<GameEvent>().HasOne(e => e.Game);
    modelBuilder.Entity<TeamEvent>().HasOne(e => e.Event);
    modelBuilder.Entity<TeamEvent>().HasOne(e => e.Team);
    modelBuilder.Entity<GroupEvent>().HasOne(e => e.Event);
    modelBuilder.Entity<GroupEvent>().HasOne(e => e.Group);

    //one-to-many
    modelBuilder.Entity<Player>().HasMany(p => p.Integrations).WithOne(i => i.Player);
    modelBuilder.Entity<Player>().HasMany(p => p.Followers);
    modelBuilder.Entity<Player>().HasMany(p => p.Activities).WithOne(a => a.Player);
    modelBuilder.Entity<Game>().HasMany(g => g.GameEvents).WithOne(ge => ge.Game);
    modelBuilder.Entity<Game>().HasMany(g => g.Teams).WithOne(t => t.Game);
    modelBuilder.Entity<Team>().HasMany(t => t.TeamEvents).WithOne(te => te.Team);
    modelBuilder.Entity<Group>().HasMany(g => g.GroupEvents);

    //many to many
    modelBuilder.Entity<PlayerGames>().HasKey(pg => new {pg.PlayerId, pg.GameId});
    modelBuilder.Entity<PlayerTeams>().HasKey(pt => new {pt.PlayerId, pt.TeamId});
    modelBuilder.Entity<PlayerGroups>().HasKey(pg => new {pg.PlayerId, pg.GroupId});

    //discriminator values
    modelBuilder.Entity<Event>()
        .HasDiscriminator<string>("Type")
        .HasValue<GameEvent>("GameEvent")
        .HasValue<GroupEvent>("GroupEvent")
        .HasValue<TeamEvent>("TeamEvent");

    CALLING SEED DATA DOWN HERE, that should be fine???
}
DenLilleMand
  • 3,732
  • 5
  • 25
  • 34

2 Answers2

3

The recommended approach is to run the seeding code within a service scope in Startup.Configure().

It's like this :

using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
       var context = serviceScope.ServiceProvider.GetService<MyContext>();       
       context.Database.Migrate();
       context.EnsureSeedData();
 }

You can see more details about this on below link.

Implementing Seeding EF Core 1.0

Sampath
  • 63,341
  • 64
  • 307
  • 441
0

Agree with @Sampath's answer, but had too much to add for a quick edit to his answer. A few observations:

  1. Very similar (duplicate?) questions like this one
  2. This question+answer and the others seems specific to EF Core. That's my situation.
  3. Specifically OP shows us their previous implementation; the DbMigrationsConfiguration<TContext>.Seed() method (from EF 6); @Sampath's link makes the same observation -- so it must be common for readers to migrate from EF 5/6 to EF Core; that's also my situation.
  4. @Sampath's answer focuses on using of a new serviceScope returned from GetService(), as advised in @Sampath's link, which is a pattern created in response to this git issue, where the user had already disposed of their DbContext. This pattern is also seen in other answers, all of which (except for this one) call GetService() or GetRequiredService() and I don't think that's necessary.

My Answer:

You can get your DbContext directly from dependency injection into Startup.Configure(...)

Neither @Sampath nor either of the two links explain what happens in context.EnsureSeedData() (maybe that's obvious to some readers, but not for me). So I explain below

I believe the intro to EF (Core) MVC offers a solution; it creates:

  1. a static class DbInitializer
  2. a class ... with one static function called Initialize which "ensures the seed data is there". And so, its details (see below) were helpful substitute for the implementation of EnsureSeedData() method; maybe other users will find it helpful
  3. a static function ... which is passed a DbContext (which is presumably not disposed; if you've disposed of it you should probably follow @Sampath's answer and/or the others, which will GetService()), and possibly introduce a new using block
  4. a static function... which is called in (at the end?) of Startup.Configure() -- same as @Sampath's

The static Initialize aka "ensure seed" function does these key things:

  1. Ensures database is created: context.Database.EnsureCreated(); this achieves "ensure"
  2. Checks for any rows in one table (you might want to check in each table, you might want to check for specific rows) and if there are rows (assumes this function was already executed, and so return; this means it is "idempotent" and can be safely executed multiple times on each startup , but only seed once
  3. If there were not any rows; it inserts some rows; this achieves the "seed"; guarantees there are rows (by adding object literals and calling DbContext.SaveChanges()

As seen here:

public static class DbInitializer
{
    public static void Initialize(SchoolContext context)
    {
        context.Database.EnsureCreated();

        // Look for any students.
        if (context.Students.Any())
        {
            return;   // DB has been seeded
        }

        var students = new Student[]
        {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")}, ...
        };
        foreach (Student s in students)
        {
            context.Students.Add(s);
        }
        context.SaveChanges();
        ...

Then call the function with a dependency-injected DbContext like this:

First, add the context to the method signature so that ASP.NET dependency injection can provide it to your DbInitializer class.2

public void Configure(..., ..., SchoolContext context)
{

Then call your DbInitializer.Initialize method at the end of the Configure method.

    ...
    DbInitializer.Initialize(context);
}
Nate Anderson
  • 18,334
  • 18
  • 100
  • 135